#include "stdafx.h"
#include "deque"
#include "process.h"
#include "../LibHttpServer/HttpServer.h"
#include "HomeServerClass.h"
#include "../GoogleProcotol/GoogleProtocol/GoogleProtocol/GoogleAPI.h"
#include "../../../Components/Common/LibCommon/util.h"

#include "PublicIPInterface.h"
#include <natupnp.h>
#include <comutil.h>

#define STOP_SERVER			0x100
#define START_LOGIN			0x101

// This global pointer is used by request bridge to send request to
// CHomeServer instance. 
// Only one instance can be exist in a process.
CHomeServer * g_Server = NULL;

// eHResult error code description
struct eHSResultDescription
{
	eHSResult code;
	wchar_t  description[256];
};

eHSResultDescription g_ResDescription[] =
{
	{HS_SUCCESS, L"Success"},
	{HS_FAILED, L"General Failed"},
	{HS_HTTP_ERROR, L"HTTP Server general error"},
	{HS_HTTP_PORT_ERROR, L"HTTP Server port error"},
};

CHomeServer::CHomeServer()
{
	m_hMainThread = NULL;
	m_hAnnounceIPThread = NULL;
}

CHomeServer::~CHomeServer()
{
}

eHSResult CHomeServer::StartServer()
{
	// If the Main Server Thread already started, ignore this calling.
	if (m_hMainThread)
		return HS_SUCCESS;
	// Start up the Main Server Thread
	m_hMainThread = (HANDLE)_beginthreadex(NULL, 0, CHomeServer::MainServerThread, this, 0, NULL);
	return HS_SUCCESS;
}

eHSResult CHomeServer::StopServer()
{
	// If the Main Server Thread alreay stopped, ignore this calling.
	if (!m_hMainThread)
		return HS_SUCCESS;
	LOGSTR(*m_pLog, LV_INFO, "CHomeServer::StopServer::Send Stop signal to thread");
	// Send stop event to main server thread.
	m_MainThreadEventQueue.Push(&CThreadEvent(STOP_SERVER, 0));

	// Wait the thread to stop.
	WaitForSingleObject(m_hMainThread, INFINITE);
	CloseHandle(m_hMainThread);
	m_hMainThread = 0;
	LOGSTR(*m_pLog, LV_INFO, "CHomeServer::StopServer::Server Stopped");
	return HS_SUCCESS;
}

eHSResult CHomeServer::WaitForStop()
{
	DWORD ExitCode = 0;
	if (m_hMainThread)
	{
		WaitForSingleObject(m_hMainThread, INFINITE);
		GetExitCodeThread(m_hMainThread, &ExitCode);
	}
	eHSResult ErrCode = (eHSResult)ExitCode;
	return ErrCode;
}

const wchar_t * CHomeServer::GetErrorCode(eHSResult code)
{
	unsigned int a = sizeof(g_ResDescription);
	unsigned int size_list = sizeof(g_ResDescription)/sizeof(g_ResDescription[0]);
	for (unsigned int i = 0; i < size_list; i++)
	{
		if (code == g_ResDescription[i].code)
			return g_ResDescription[i].description;
	}
	return L"Unknown Error";
}

eHSResult CHomeServer::Login(const wchar_t * username, const wchar_t * passwd)
{
	m_Status.m_LoginStatus = CServerStatus::Account_Logging;
	m_Config.m_Username = username;
	m_Config.m_Password = passwd;

	LOGSTR(*m_pLog, LV_INFO, "CHomeServer::Login::Send signal to login");

	// Send event to main thread.
	CThreadEvent Event(START_LOGIN, 0);
	m_MainThreadEventQueue.Push(&Event);
	// Wait for the result.
	eHSResult Result = Event.Wait();
	LOGSTR(*m_pLog, LV_INFO, "CHomeServer::Login::Login Finsihed");
	return Result;	
}

eHSResult CHomeServer::Logout()
{
	if (m_Status.m_LoginStatus == CServerStatus::Account_Online)
	{
		LRESULT res = ::LogOut();
		if (res <= 0)
			return HS_FAILED;
		else
		{
			// Update the status.
			m_Status.m_LoginStatus = CServerStatus::Account_Offline;
		}
	}
	return HS_SUCCESS;
}

CServerStatus CHomeServer::GetStatus()
{
	return m_Status;
}

const wchar_t * CHomeServer::GetShareFolder(int index)
{
	if (index >= m_Config.m_ShareFolders.size())
		return NULL;
	return m_Config.m_ShareFolders[index].c_str();
}

eHSResult CHomeServer::AddShareFolder(const wchar_t * folder)
{
	m_Config.AddShareFolder(std::wstring(folder));
	m_Config.SaveConfig();
	return HS_SUCCESS;
}

eHSResult CHomeServer::RemoveShareFolder(const wchar_t * folder)
{
	m_Config.RemoveShareFolder(std::wstring(folder));
	m_Config.SaveConfig();
	return HS_SUCCESS;
}

const wchar_t * CHomeServer::GetConfigPath()
{
	return m_Config.m_ConfigPath.c_str();
}

unsigned int CHomeServer::MainServerThread(void* p)
{
	CHomeServer * pServer = (CHomeServer*)p;
	LOGSTR(*pServer->m_pLog, LV_INFO, "CHomeServer::MainServerThread::Started");
	bool res = false;
	bool autologin = false;
	// Loading Settings.
	res = pServer->m_Config.LoadConfig();

	// Check local ip list.
	pServer->GetLocalIPList();

	InitHttpServer();

	// Bridge functions.
	BindDllFunction("/Content","ParseServer.dll","ParseServerContent");
	BindDllFunction("/Root","ParseServer.dll","GetShareFolders");
	BindDllFunction("/Thumbnail","ParseServer.dll","GetThumbnail");
	BindDllFunction("/Download", "ParseServer.dll","DownloadFile");
	BindDllFunction("/server/login", "ParseServer.dll", "LoginToGoogle");
	BindDllFunction("/server/status","ParseServer.dll", "GetServerStatus");
	BindDllFunction("/server/addfolder","ParseServer.dll", "AddFolder");
	BindDllFunction("/server/removefolder", "ParseServer.dll", "RemoveFolder");
	BindDllFunction("/server/stop", "ParseServer.dll", "StopServer");

	// Start up the HTTP Server.
	char logfile[256];
	if (GetFileAttributes(L"c:\\log") != INVALID_FILE_ATTRIBUTES)
	{
		lstrcpyA(logfile, "c:\\log\\http.log");
	}
	else
		logfile[0] = 0;
	int HttpStartRes = StartHttpServer(pServer->m_Config.m_Port, logfile);
	if (HttpStartRes < 0)
	{
		LOGSTR(*pServer->m_pLog, LV_ERROR, "CHomeServer::MainServerThread::StartHTTPServer error");
		if (HttpStartRes == -1)
			return HS_HTTP_ERROR;
		else if (HttpStartRes == -2)
			return HS_HTTP_PORT_ERROR;
		return HS_HTTP_ERROR;
	}

	if (!pServer->m_Config.m_Username.empty() && 
		!pServer->m_Config.m_Token.empty())
	{
		autologin = true;
		// Launch the panel as system tray icon, and only
		// perform backgound login.
	}
	else
	{
		// Launch the panel with normal window. And prompt user to login.
	}

	if (autologin)
	{
		// change the status.
		// Start login 
	}
	
	// Start the public detection

	CThreadEvent *pEvent = NULL;
	// While loop to process each events.
	while (pServer->m_MainThreadEventQueue.Pop(&pEvent, INFINITE))
	{
		switch (pEvent->m_EventID)
		{
		case STOP_SERVER:
			// Stop all working thread.
			pServer->StopWorkThread();
			pEvent->m_Result = HS_SUCCESS;
			break;
		case START_LOGIN:
			pEvent->m_Result = pServer->InternalLogin();
			break;
		}
		int nEventId = pEvent->m_EventID;
		pEvent->Complete();
	//	Sleep(4000);
		if (nEventId == STOP_SERVER)
			break;
	}
	return 0;
}
BOOL CHomeServer::HandleAnounceIP()
{
	m_Status.m_IPStatus = CServerStatus::Detecting;
	// Detect the IP address of this server.
	eHSResult HSRes = InternalDetectIP();
	if (HSRes != HS_SUCCESS)
	{
		LOGSTR(*m_pLog, LV_ERROR, "CHomeServer::AnnounceIPThread::Can not Detect public IP.");
		m_Status.m_IPStatus = CServerStatus::Unavail;
		return FALSE;
	}
	m_Status.m_IPStatus = CServerStatus::Avail;

	// Open UPnP NAT if NAT is exist.
	if (IsBehindNAT())
	{
		m_Status.m_NATTStatus = CServerStatus::Detecting;
		m_Status.m_HasNAT = true;
		LOGSTR(*m_pLog, LV_INFO, "CHomeServer::AnnounceIPThread::This PC is behaind NAT.");
		HSRes = OpenUPnPNAT();
		if (HSRes != HS_SUCCESS)
		{
			LOGSTR(*m_pLog, LV_ERROR, "CHomeServer::AnnounceIPThread::Can not Open NAT by UPnP.");
			m_Status.m_NATTStatus = CServerStatus::Unavail;
		}
		else
		{
			m_Status.m_NATTStatus = CServerStatus::Avail;
		}
	}

	// Announce the IP address to google account.
	m_Status.m_AnnounceStatus = CServerStatus::Announce_Going;
	std::string HomeUrl = "http://";
	HomeUrl += m_Status.m_PublicIP;
	char port[8];
	itoa(m_Config.m_Port, port, 10);
	HomeUrl += ":";
	HomeUrl += port;
	HomeUrl += "/";
	LRESULT res = ::SetIP("HomeMediaServer", HomeUrl.c_str());
	if (res <= 0)
	{
	//	TCHAR sza[MAX_PATH];
	//	_stprintf(sza,_T("CHomeServer::AnnounceIPThread::Fail to announce public IP, Error code = %d"),res);

		LOGSTR1(*m_pLog, LV_ERROR, "CHomeServer::AnnounceIPThread::Fail to announce public IP, Error code = %d",res);
		m_Status.m_AnnounceStatus = CServerStatus::Announce_Fail;
		return FALSE;
	}
	else
	{
		m_Status.m_AnnounceStatus = CServerStatus::Announce_OK;
		m_Status.m_AnnounceIP = m_Status.m_PublicIP;
		m_Status.m_MainStatus = CServerStatus::Main_Ready;
	}
	LOGSTR(*m_pLog, LV_INFO, "CHomeServer::AnnounceIPThread::Home Server is Ready");	
	return TRUE;
}
unsigned int CHomeServer::AnnounceIPThread(void * p)
{
	CHomeServer * pServer = (CHomeServer*)p;
	BOOL bsuc= pServer->HandleAnounceIP();
	if (!bsuc)
	{
		//try again 
		bsuc = pServer->HandleAnounceIP();
	}
	if (!bsuc)
		::MessageBox(NULL,L"AnnounceIP had some error",NULL,MB_OK);
	return bsuc;
/*	pServer->m_Status.m_IPStatus = CServerStatus::Detecting;
	// Detect the IP address of this server.
	eHSResult HSRes = pServer->InternalDetectIP();
	if (HSRes != HS_SUCCESS)
	{
		LOGSTR(*pServer->m_pLog, LV_ERROR, "CHomeServer::AnnounceIPThread::Can not Detect public IP.");
		pServer->m_Status.m_IPStatus = CServerStatus::Unavail;
		return -1;
	}
	pServer->m_Status.m_IPStatus = CServerStatus::Avail;

	// Open UPnP NAT if NAT is exist.
	if (pServer->IsBehindNAT())
	{
		pServer->m_Status.m_NATTStatus = CServerStatus::Detecting;
		pServer->m_Status.m_HasNAT = true;
		LOGSTR(*pServer->m_pLog, LV_INFO, "CHomeServer::AnnounceIPThread::This PC is behaind NAT.");
		HSRes = pServer->OpenUPnPNAT();
		if (HSRes != HS_SUCCESS)
		{
			LOGSTR(*pServer->m_pLog, LV_ERROR, "CHomeServer::AnnounceIPThread::Can not Open NAT by UPnP.");
			pServer->m_Status.m_NATTStatus = CServerStatus::Unavail;
		}
		else
		{
			pServer->m_Status.m_NATTStatus = CServerStatus::Avail;
		}
	}

	// Announce the IP address to google account.
	pServer->m_Status.m_AnnounceStatus = CServerStatus::Announce_Going;
	std::string HomeUrl = "http://";
	HomeUrl += pServer->m_Status.m_PublicIP;
	char port[8];
	itoa(pServer->m_Config.m_Port, port, 10);
	HomeUrl += ":";
	HomeUrl += port;
	HomeUrl += "/";
	LRESULT res = ::SetIP("HomeMediaServer", HomeUrl.c_str());
	if (res <= 0)
	{
		LOGSTR(*pServer->m_pLog, LV_ERROR, "CHomeServer::AnnounceIPThread::Fail to announce public IP.");
		pServer->m_Status.m_AnnounceStatus = CServerStatus::Announce_Fail;
	}
	else
	{
		pServer->m_Status.m_AnnounceStatus = CServerStatus::Announce_OK;
		pServer->m_Status.m_AnnounceIP = pServer->m_Status.m_PublicIP;
		pServer->m_Status.m_MainStatus = CServerStatus::Main_Ready;
	}
	LOGSTR(*pServer->m_pLog, LV_INFO, "CHomeServer::AnnounceIPThread::Home Server is Ready");
	return 0;*/
}

eHSResult CHomeServer::StopWorkThread()
{
	return HS_FAILED;
}

eHSResult CHomeServer::InternalLogin()
{
	m_Status.m_MainStatus = CServerStatus::Main_Connecting;
	// Login
	std::string username = WideCharToMultiChar(m_Config.m_Username);
	std::string password = WideCharToMultiChar(m_Config.m_Password);
	m_Status.m_LoginStatus = CServerStatus::Account_Logging;
	LRESULT res = ::LogIn(username.c_str(), password.c_str());
	if (res <= 0)
	{
	//	TCHAR sz[MAX_PATH];
	//	_stprintf(sz,L"CHomeServer::InternalLogin::Fail to login, Error code = %d",res);

		LOGSTR1(*m_pLog, LV_ERROR, "CHomeServer::InternalLogin::Fail to login, Error code = %d",res);
		m_Status.m_LoginStatus = CServerStatus::Account_Offline;
		return HS_FAILED;
	}
	m_Status.m_LoginStatus = CServerStatus::Account_Online;
	if (m_hAnnounceIPThread)
	{
		// For debug, we wait it infintely.
		WaitForSingleObject(m_hAnnounceIPThread, INFINITE);
		CloseHandle(m_hAnnounceIPThread);
		m_hAnnounceIPThread=NULL;

	}
	m_hAnnounceIPThread = (HANDLE)_beginthreadex(NULL, 0, CHomeServer::AnnounceIPThread, 
												this, 0, NULL);
	// Wait this thread finished
	WaitForSingleObject(m_hAnnounceIPThread, INFINITE);
	if (m_Status.m_MainStatus == CServerStatus::Main_Ready)
		return HS_SUCCESS;
	else
		return HS_FAILED;
	return HS_FAILED;

}

eHSResult CHomeServer::InternalLogout()
{
	m_Status.m_MainStatus = CServerStatus::Main_Off;
	m_Status.m_LoginStatus = CServerStatus::Account_Offline;
	m_Status.m_AnnounceStatus = CServerStatus::Announce_Fail;
	m_Status.m_IPStatus = CServerStatus::Unavail;
	return HS_FAILED;
}

eHSResult CHomeServer::InternalDetectIP()
{
	std::string PubIP;
	bool res = GetPublicIP(PubIP);
	if (!res)
		return HS_FAILED;
	// Validate the IP format.
	std::string::size_type pos = 0;
	for (int i = 0; i < 3; i++)
	{
		if (pos >= PubIP.size())
		{
			LOGSTR(*m_pLog, LV_ERROR, "CHomeServer::InternalDetectIP::IP format error");
			return HS_FAILED;
		}
		std::string::size_type pos_e = PubIP.find_first_of('.', pos);
		if (pos_e == std::string::npos)
		{
			LOGSTR(*m_pLog, LV_ERROR, "CHomeServer::InternalDetectIP::IP format error");
			return HS_FAILED;
		}
		std::string mark = PubIP.substr(pos, pos_e - pos + 1);
		int v = atoi(mark.c_str());
		if (v < 0 || v > 255)
		{
			LOGSTR(*m_pLog, LV_ERROR, "CHomeServer::InternalDetectIP::IP format error");
			return HS_FAILED;
		}
		pos = pos_e+1;
	}
	// Update server status.
	m_Status.m_PublicIP = PubIP;
	m_Status.m_LocalIP = m_LocalIPList.empty()?"":m_LocalIPList[0];
	std::wstring ip = MultiCharToWideChar(m_Status.m_PublicIP);
	LOGSTR1(*m_pLog, LV_INFO, "CHomeServer::InternalDetectIP::Got Public IP : %s", ip.c_str());
	ip = MultiCharToWideChar(m_Status.m_LocalIP);
	LOGSTR1(*m_pLog, LV_INFO, "CHomeServer::InternalDetectIP::Got Local IP : %s", ip.c_str());
	return HS_SUCCESS;
}

bool CHomeServer::IsBehindNAT()
{
	for (int i = 0; i< m_LocalIPList.size(); i++)
	{
		if (m_LocalIPList[i] == m_Status.m_PublicIP)
			return false;
	}
	return true;
}

eHSResult CHomeServer::OpenUPnPNAT()
{
	LOGSTR(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::Opening NAT by UPnP...");
	unsigned short ExternalPort = 8080;
	unsigned short ServicePort = 8080;
	bool bPublicNetwork = false;
	IUPnPNAT* pIUPnPNAT = NULL;
	IStaticPortMappingCollection *pISPMC = NULL;
	IStaticPortMapping* pIPM = NULL;
	HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED); 
	if (FAILED(hr))
	{
		LOGSTR1(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::CoInitializeEx() failed : hr = 0x%x", hr);
	}
	hr = CoCreateInstance(CLSID_UPnPNAT,NULL,CLSCTX_INPROC_SERVER,IID_IUPnPNAT,(LPVOID*)&pIUPnPNAT); 
	if (hr == S_OK) 
	{ 
		hr = pIUPnPNAT->get_StaticPortMappingCollection(&pISPMC);
		if (pISPMC != NULL)
		{
			LOGSTR(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::The NAT support UPnP");
			_bstr_t protocol;
			protocol = L"TCP";
			_bstr_t hname;
			hname = m_Status.m_LocalIP.c_str();

			// Loop to find the available port number.
			unsigned int loop_count = 0;
			while (loop_count++ < 20)
			{
				hr = pISPMC->get_Item(ExternalPort, protocol, &pIPM);
				if (pIPM == NULL)
				{
					LOGSTR1(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::NAT don't have port mapping for port %u", (unsigned int)ExternalPort);
					// Try Add a port forwarding.
					hr = pISPMC->Add(ExternalPort, protocol, ServicePort, 
									hname, VARIANT_TRUE , _bstr_t("docomedia port forwarding"),
									&pIPM);
					if (pIPM != NULL)
					{
						// Addtion is successful.
						bPublicNetwork = true;
						break;
					}
					else
					{
						// In some NAT device, the return value is not trusted,
						// we should check the result directly by query the mapping.
						hr = pISPMC->get_Item(ExternalPort, protocol, &pIPM);
						if (pIPM != NULL)
						{
							long internal_port = 0;
							_bstr_t internal_client;
							pIPM->get_InternalPort(&internal_port);
							pIPM->get_InternalClient(&internal_client.GetBSTR());
							if (internal_port == ServicePort && 
								internal_client == hname)
							{
								bPublicNetwork = true;
								break;
							}
						}
					}
				}
				else if (pIPM != NULL)
				{
					LOGSTR1(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::NAT already has port mapping for port %u", (unsigned int)ExternalPort);
					long internal_port = 0;
					_bstr_t internal_client;
					pIPM->get_InternalPort(&internal_port);
					pIPM->get_InternalClient(&internal_client.GetBSTR());
					if (internal_port == ServicePort && 
						internal_client == hname)
					{
						bPublicNetwork = true;
						break;
					}
					else
					{
						LOGSTR(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::Try to change port forwarding");
						// Edit the internal port and client.
						hr = pIPM->EditInternalPort(ServicePort);
						if (FAILED(hr))
							break;
						hr = pIPM->EditInternalClient(hname);
						if (FAILED(hr))
							break;
						bPublicNetwork = true;
						break;
					}
				}
				// Change the port number.
				ExternalPort++;
			}//while (loop_count++ < 100)
		}// if (pISPMC)
		else
		{
			LOGSTR(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::UPnP Discovery failed.");
		}
	} 
	else
	{
		LOGSTR1(*m_pLog, LV_INFO, "CHomeServer::OpenUPnPNAT::Can not create Instance : hr = 0x%x", hr);
	}
	if (pIUPnPNAT) 
		pIUPnPNAT->Release();
	if (pISPMC)
		pISPMC->Release();
	if (pIPM)
		pIPM->Release();
	CoUninitialize();
	if (bPublicNetwork)
		return HS_SUCCESS;
	else 
		return HS_FAILED;
}
  
void CHomeServer::GetLocalIPList()
{
	char hname[256];
	if (::gethostname(hname,sizeof(hname)-1) != 0)
		return;
	char servname[128];
	memset(servname, 0, 128);
	addrinfo hint;
	memset(&hint, 0, sizeof(addrinfo));
	hint.ai_family = AF_INET;
	hint.ai_socktype = SOCK_STREAM;
	hint.ai_protocol = IPPROTO_TCP;
	addrinfo * pAddrInfo = 0;
	addrinfo * pAddrInfoBase = 0;
	int retval = getaddrinfo(hname, NULL, &hint, &pAddrInfo);
	if (retval != 0)
	{
		return; 
	}
	pAddrInfoBase = pAddrInfo;
	unsigned int fillindex = 0;
	while (pAddrInfo != NULL)
	{
		sockaddr_in *paddr;
		paddr = (sockaddr_in*)pAddrInfo->ai_addr;
		pAddrInfo = pAddrInfo->ai_next;
		if (paddr->sin_addr.s_addr != 0x0100007f)
		{
			std::string ip = inet_ntoa(paddr->sin_addr);
			m_LocalIPList.push_back(ip);
		}
	}
}

void CHomeServer::SetLog(tLog * pLog)
{
	m_pLog = pLog;
}
